package com.wlyuan.index.domain.index.builder;


import com.wlyuan.core.domain.event.DomainEventPublisher;
import com.wlyuan.core.domain.valueobj.CombineId;
import com.wlyuan.index.api.dto.QuotationIndexDTO;
import com.wlyuan.index.api.dto.waybill.BalanceIndexDTO;
import com.wlyuan.index.api.dto.waybill.BillIndexDTO;
import com.wlyuan.index.api.dto.waybill.WaybillArgumentsDTO;
import com.wlyuan.index.api.dto.waybill.WaybillIndexDTO;
import com.wlyuan.index.domain.index.WaybillIndex;
import com.wlyuan.index.domain.index.event.WaybillIndexDamaged;
import com.wlyuan.index.domain.index.valueobj.*;
import com.wlyuan.index.domain.source.Source;
import com.wlyuan.index.domain.source.adapter.BalanceAdapter;
import com.wlyuan.index.domain.source.adapter.BillAdapter;
import com.wlyuan.index.domain.source.adapter.QuotationAdapter;
import com.wlyuan.index.domain.source.adapter.WaybillAdapter;
import com.wlyuan.index.domain.source.utils.SourceUtils;
import com.wlyuan.index.domain.source.valueobj.Highlight;
import com.wlyuan.transport.dto.WaybillPriceDetailDTO;
import com.wlyuan.transport.dto.common.WaybillCacheDTO;
import com.wlyuan.transport.dto.query.*;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * @author yuanjie
 */
@NoArgsConstructor
@Slf4j
public class WaybillIndexBuilder {
    @Mapper(imports = {Bill.class}, uses = {SourceUtils.class})
    public interface Assembler {


        @Mappings({
                @Mapping(target = "combine", ignore = true),
                @Mapping(target = "combined", source = "combined", defaultValue = "false"),
                @Mapping(target = "trackNo", source = "device.deviceCode"),
                @Mapping(target = "trackType", source = "device.trackType"),
                @Mapping(target = "closeStatus", source = "close.status"),
                @Mapping(target = "closeType", source = "close.type"),
                @Mapping(target = "bookingStatus", source = "pickCovenant.pickCovenantStatus"),
                @Mapping(target = "bookingArrivalTime", source = "pickCovenant.arrivalTime"),
                @Mapping(target = "bookingEnabled", source = "showPickCovenant"),
                @Mapping(target = "consigneeName", source = "consignee.consigneeName"),
                @Mapping(target = "consigneeId", source = "consignee.consigneeId"),
                @Mapping(target = "hasQuote", source = "waybillPrice.quotationStatus"),
                @Mapping(target = "endBill", expression = "java(Bill.ENDED.equals(source.getBillDayStatus()))"),
                @Mapping(target = "affiliated", source = "followWaybill", defaultValue = "false"),
                @Mapping(target = "original", source = "original", defaultValue = "false")
        })
        void assemble(@MappingTarget WaybillIndex index, WaybillAdapter source);

        //        @Mapping(target = "customerName", source = "showCustomerName")
        void assemble(@MappingTarget WaybillIndex index, WaybillCustomerDTO source);

        void assemble(@MappingTarget WaybillIndex index, WaybillCarrierDTO source);

        void assemble(@MappingTarget WaybillIndex index, WaybillCacheDTO source);

        @Mapping(target = "tenantId", ignore = true)
        void assemble(@MappingTarget WaybillIndex index, WaybillPriceDetailDTO source);

        void assemble(@MappingTarget WaybillIndex index, WaybillArgumentsDTO source);


        @Mappings({
                @Mapping(target = "type", source = "signType"),
                @Mapping(target = "quantity", source = "totalQuantity"),
                @Mapping(target = "signBy", source = "signBy.userId"),
        })
        WaybillSign assembleSign(WaybillSignDetailDTO source);

        @Mappings({
                @Mapping(target = "type", source = "receiptType"),
                @Mapping(target = "quantity", source = "totalQuantity"),
                @Mapping(target = "receiptBy", source = "createBy.userId"),
        })
        WaybillReceipt assembleReceipt(WaybillReceiptDetailDTO source);


        @Mappings({
                @Mapping(target = "endQuote", source = "closeQuote"),
        })
        WaybillRelation assembleRelation(WaybillRelationEventDTO source);

        @Mappings({
                @Mapping(target = "quotations", ignore = true),
                @Mapping(target = "bills", ignore = true),
//                @Mapping(target = "balances", ignore = true),
        })
        void assembleRelation(@MappingTarget WaybillRelation relation, WaybillRelation source);

        @Mappings({
                @Mapping(source = "billName", target = "name")
        })
        Bill assembleBill(BillAdapter source);

        @Mapping(target = "source", source = "sourceType")
        Quotation assembleQuotation(QuotationAdapter source);

        @Mappings({
                @Mapping(source = "combineContractNo", target = "contractNo")
        })
        WaybillIndex.Combine assembleCombine(WaybillAdapter waybill);
    }

    private static final Assembler ASSEMBLER = Mappers.getMapper(Assembler.class);
    private WaybillIndex index = new WaybillIndex();
    private WaybillAdapter waybill;
    private final List<QuotationAdapter> quotations = new ArrayList<>();
    private final List<BillAdapter> bills = new ArrayList<>();
    private final List<BalanceAdapter> balances = new ArrayList<>();


    public WaybillIndexBuilder(Source source) {
        this.waybill = new WaybillAdapter(source.decode(WaybillIndexDTO.class));
    }

    public WaybillIndexBuilder withIndex(WaybillIndex index) {
        if (null != index) {
            this.index = index;
        }
        return this;
    }

    public WaybillIndexBuilder withBound(Source source) {
        if (null == source) {
            return this;
        }

        if (source.isQuotation()) {
            this.quotations.add(new QuotationAdapter(source.decode(QuotationIndexDTO.class)));
        } else if (source.isBill()) {
            this.bills.add(new BillAdapter(source.decode(BillIndexDTO.class)));
        } else if (source.isBalance()) {
            this.balances.add(new BalanceAdapter(source.decode(BalanceIndexDTO.class)));
        }
        return this;
    }

    public WaybillIndexBuilder withBounds(List<Source> sources) {
        if (CollectionUtils.isEmpty(sources)) {
            return this;
        }
        sources.forEach(this::withBound);
        return this;
    }

    private void addRelation(WaybillRelation relation, WaybillRelationEventDTO source) {
        this.addExceptions(relation, source);
        // 将新的数据合并到已存在数据中
        this.index.addRelation(relation, ASSEMBLER::assembleRelation);
    }


    private void buildAddress(WaybillIndexDTO waybill) {
        if (null != waybill.getFromContact()) {
            var contact = waybill.getFromContact();
            this.index.setFromContact(contact.getContactName(), contact.getContactMobile());
        }

        if (null != waybill.getToContact()) {
            var contact = waybill.getToContact();
            this.index.setToContact(contact.getContactName(), contact.getContactMobile());
        }
    }

    private void addExceptions(WaybillRelation relation, WaybillRelationEventDTO source) {
        if (null == source) {
            return;
        }

        relation.addHighlight(Highlight.SIGN_EXCEPTION, source.getSignExceptionStatus());
        relation.addHighlight(Highlight.RECEIPT_PHOTO, source.getReceiptAttachmentStatus());
        relation.addHighlight(Highlight.RECEIPT_LOST, source.getReceiptLoseStatus());
        relation.addHighlight(Highlight.RECEIPT_RECOVERED, source.getReceiptReplenishStatus());
        relation.addHighlight(Highlight.RECEIPT_REVIEWED, source.getReceiptReviewStatus());
    }

    private void buildRelations(WaybillAdapter waybill) {
        var customer = waybill.getCustomer();
        assert null != customer;

        var tenantIds = new ArrayList<Long>();
        if (waybill.hasDownstream()) {
            var relation = ASSEMBLER.assembleRelation(waybill.getDownstream());
            // 如果关系上没有customerName, 取运单上的customerName
            if (StringUtils.isEmpty(relation.getCustomerName())) {
                relation.setCustomerName(customer.getCustomerName());
            }
            relation.setCharacter(Characters.CARRIER);
            this.addRelation(relation, waybill.getDownstream());
            tenantIds.add(relation.getTenantId());
        }

        if (waybill.hasUpstream()) {
            var relation = ASSEMBLER.assembleRelation(waybill.getUpstream());
            // 如果关系上没有customerName, 取运单上的customerName
            if (StringUtils.isEmpty(relation.getCustomerName())) {
                relation.setCustomerName(customer.getCustomerName());
            }
            relation.setCharacter(Characters.CUSTOMER);
            this.addRelation(relation, waybill.getUpstream());
            tenantIds.add(relation.getTenantId());
        }

        this.index.checkRelations(tenantIds);
    }

    private void buildRelations(QuotationAdapter adapter) {
        if (CollectionUtils.isEmpty(adapter.getRelations())) {
            logger.error("Quotation relations no found: {}", adapter);
            return;
        }

//        // 草稿状态只添加所属方
//        if (adapter.isDraft()) {
//            var quotation = ASSEMBLER.assembleQuotation(adapter);
//            this.index.addQuotation(quotation, adapter.getTenantId());
//            return;
//        }

        adapter.getRelations().forEach(relation -> {
            var quotation = ASSEMBLER.assembleQuotation(adapter);
            quotation.setStatus(relation.getStatus());
            this.index.addQuotation(quotation, relation.getTenantId());
        });
    }

    private void buildRelations(BillAdapter adapter) {
        if (null != adapter.getUpstream()) {
            var bill = ASSEMBLER.assembleBill(adapter);
            var upstream = adapter.getUpstream();
            bill.setStatus(upstream.getStatus());
            this.index.addBill(bill, upstream.getTenantId());
        }

        if (null != adapter.getDownstream()) {
            var bill = ASSEMBLER.assembleBill(adapter);
            var downstream = adapter.getDownstream();
            bill.setStatus(downstream.getStatus());
            this.index.addBill(bill, downstream.getTenantId());
        }
    }

    private void buildCombine(WaybillAdapter waybill) {
        if (!waybill.hasCombine()) {
            return;
        }

        this.index.addCombine(ASSEMBLER.assembleCombine(waybill));
    }

    private void buildRelations(BalanceAdapter adapter) {
        this.index.addBalance(Balances.PREPAY, adapter.getPrepayFeeFlag());
        this.index.addBalance(Balances.COLLECTION, adapter.getCollectionFeeFlag());
        this.index.addBalance(Balances.ARRIVE_PAY, adapter.getArrivePayFeeFlag());
        this.index.addBalance(Balances.RECEIPT, adapter.getReceiptFeeFlag());
        this.index.addBalance(Balances.OVERALL, adapter.getWaybillFeeFlag());

//        if (CollectionUtils.isNotEmpty(adapter.getRelations())) {
//            adapter.getRelations().forEach(source -> {
//                var reconcile = new Reconcile(source.getFinanceStatus());
//                this.index.addReconcile(reconcile, source.getTenantId());
//            });
//        }
    }

    public WaybillIndex build() {
        if (null != this.waybill) {
            ASSEMBLER.assemble(this.index, this.waybill);
            ASSEMBLER.assemble(this.index, this.waybill.getCarrier());
            ASSEMBLER.assemble(this.index, this.waybill.getCustomer());
            ASSEMBLER.assemble(this.index, this.waybill.getWaybillCache());
            ASSEMBLER.assemble(this.index, this.waybill.getWaybillPrice());
            ASSEMBLER.assemble(this.index, this.waybill.getArguments());
            this.buildAddress(this.waybill);
            this.buildCombine(this.waybill);
            this.buildRelations(this.waybill);
        }

        this.quotations.forEach(this::buildRelations);
        this.bills.forEach(this::buildRelations);
        this.balances.forEach(this::buildRelations);

        if (CollectionUtils.isEmpty(this.index.getRelations())) {
            logger.error("Waybill relations no found: {}", this.index.getWaybillId());
            DomainEventPublisher.publish(new WaybillIndexDamaged(this.index.getWaybillId()));
        }

        this.index.buildRelations();
        this.index.setIndexAt(LocalDateTime.now());
        return this.index;
    }

    public CombineId getCombineId() {
        if (null != this.waybill && BooleanUtils.isTrue(this.waybill.getCombined())) {
            return new CombineId(this.waybill.getCombineId());
        }

        if (null != this.index && BooleanUtils.isTrue(this.index.getCombined())) {
            return new CombineId(this.index.getCombine().getCombineId());
        }
        return null;
    }
}
